package com.night.ibatis;

import com.night.annotation.Param;
import com.night.annotation.Select;
import com.night.ibatis.typeHandler.IntegerTypeHandler;
import com.night.ibatis.typeHandler.LongTypeHandler;
import com.night.ibatis.typeHandler.StringTypeHandler;
import com.night.ibatis.typeHandler.TypeHandler;
import com.night.utils.StringUtils;

import java.lang.reflect.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: chen yang
 * @Date: 2024/12/15 11:11
 */
public class MapperProxyFactory {

    public static Map<Class, TypeHandler> typeHandlerMap = new HashMap<>();

    static {
        typeHandlerMap.put(String.class, new StringTypeHandler());
        typeHandlerMap.put(Integer.class, new IntegerTypeHandler());
        typeHandlerMap.put(Long.class, new LongTypeHandler());
    }

    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T getMapper(Class<T> mapper){
        Object instance = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, (proxy, method, args) -> {
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true", "root", "root");
            Select select = method.getAnnotation(Select.class);

            if (select == null) {
                return null;
            }

            String originSql = select.value();

            // 解析参数
            HashMap<String, Object> paramValueMapping = getParamValueMapping(method, args);

            PreparedStatement statement = buildStatement(connection, originSql, paramValueMapping);

            statement.execute();

            Class<?> resultType = getResultType(method);

            if (resultType == null) {
                return null;
            }

            // 处理返回类型
            ResultSet resultSet = statement.getResultSet();

            // 获取查询出来的字段名
            ResultSetMetaData metaData = resultSet.getMetaData();
            List<String> columnNames = new ArrayList<>();
            for (int i = 0; i < metaData.getColumnCount(); i++) {
                columnNames.add(metaData.getColumnName(i + 1));
            }

            Map<String, Method> setterMethodMap = getSetterMethodMap(resultType);

            List<Object> list = new ArrayList<>();

            while (resultSet.next()) {

                Object instance1 = resultType.newInstance();

                for (String columnName : columnNames) {
                    Method setterMethod = setterMethodMap.getOrDefault(columnName, setterMethodMap.get(StringUtils.toLowerCamelCase(columnName)));
                    if (setterMethod == null) {
                        continue;
                    }
                    TypeHandler<?> typeHandler = typeHandlerMap.get(setterMethod.getParameters()[0].getType());
                    setterMethod.invoke(instance1, typeHandler.getResult(resultSet, columnName));
                }
                list.add(instance1);
            }

            if (method.getReturnType().equals(List.class)) {
                return list;
            }

            return list.getFirst();
        });
        return (T) instance;
    }



    /**
     * 获取方法参数名和实际的值
     */
    private static HashMap<String, Object> getParamValueMapping(Method method, Object[] args) {
        HashMap<String, Object> paramValueMapping = new HashMap<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            // parameter.getName() 拿到的 arg[?] 而不是参数名
            paramValueMapping.put(parameter.getName(), args[i]);

            Param param = parameter.getAnnotation(Param.class);
            if (param != null) {
                paramValueMapping.put(param.value(), args[i]);
            }
        }

        return paramValueMapping;
    }

    private static PreparedStatement buildStatement(Connection connection, String sql, HashMap<String, Object> paramValueMapping) throws SQLException {
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);

        String parsedSql = parser.parse(sql);
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();

        System.out.println("sql=>: " + parsedSql);

        PreparedStatement statement = connection.prepareStatement(parsedSql);

        setStatementParameter(statement, parameterMappings, paramValueMapping);
        return statement;
    }

    /**
     * 设置sql参数值
     */
    private static void setStatementParameter(PreparedStatement statement, List<ParameterMapping> parameterMappings, HashMap<String, Object> paramValueMapping) throws SQLException {
        for (int i = 0; i < parameterMappings.size(); i++) {
            String property = parameterMappings.get(i).getProperty();
            Object value = paramValueMapping.get(property);
            Class<?> paramValueClass = value.getClass();
            typeHandlerMap.get(paramValueClass).setParameter(statement, i + 1, value);
        }
    }


    /**
     * 获取方法返回值
     */
    private static Class<?> getResultType(Method method) {
        Class<?> resultType = null;
        Type genericReturnType = method.getGenericReturnType();
        if (genericReturnType instanceof Class) {
            resultType = (Class<?>) genericReturnType;
        } else if (genericReturnType instanceof ParameterizedType) {
            Type actualTypeArgument = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
            resultType = (Class<?>) actualTypeArgument;
        }
        return resultType;
    }


    /**
     * 获取 class 中的set方法
     */
    private static Map<String, Method> getSetterMethodMap(Class<?> resultType) {

        Map<String, Method> setterMethodMap = new HashMap<>();

        for (Method declaredMethod : resultType.getDeclaredMethods()) {
            if (declaredMethod.getName().startsWith("set")) {
                String propertyName = declaredMethod.getName().substring(3);
                propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
                setterMethodMap.put(propertyName, declaredMethod);
            }
        }
        return setterMethodMap;

    }
}
